﻿using CNative.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace CNative.DbUtils
{
    /// <summary>
    /// Table转换实体处理
    /// </summary>
    public class EntityHelper
    {
        private delegate T Load<T>(DataRow DrRecord);
        private static readonly MethodInfo mGetValueMet = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
        private static readonly MethodInfo mIsDBNullMet = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });
        private static Dictionary<Type, Delegate> mRowMapMets = new Dictionary<Type, Delegate>();
        private static Dictionary<Type, MethodInfo> mConvertMets = new Dictionary<Type, MethodInfo>()
       {
           {typeof(int),typeof(FuncStr).GetMethod("NullToInt",new Type[]{typeof(object)})},
           {typeof(Int16),typeof(Convert).GetMethod("ToInt16",new Type[]{typeof(object)})},
           {typeof(Int64),typeof(Convert).GetMethod("ToInt64",new Type[]{typeof(object)})},
           {typeof(DateTime),typeof(Convert).GetMethod("ToDateTime",new Type[]{typeof(object)})},
           //  {typeof(DateTime?),typeof(Convert).GetMethod("ToDateTime",new Type[]{typeof(object)})},
           {typeof(decimal),typeof(FuncStr).GetMethod("NullToDecimal",new Type[]{typeof(object)})},
           {typeof(double),typeof(FuncStr).GetMethod("NullToDouble",new Type[]{typeof(object)})},
           {typeof(Boolean),typeof(Convert).GetMethod("ToBoolean",new Type[]{typeof(object)})},
           {typeof(char),typeof(Convert).GetMethod("ToChar",new Type[]{typeof(object)})},
           {typeof(string),typeof(Convert).GetMethod("ToString",new Type[]{typeof(object)})},
           {typeof(byte),typeof(Convert).GetMethod("ToByte",new Type[]{typeof(object)})},
           {typeof(Single),typeof(Convert).GetMethod("ToSingle",new Type[]{typeof(object)})}
       };
        internal static TEnum ToEnum<TEnum, TUnder>(object obj)
        {
            return (TEnum)Convert.ChangeType(obj, typeof(TUnder));
        }
        internal static TEnum StrToEnum<TEnum>(object value) where TEnum : struct
        {
            if (Enum.TryParse<TEnum>(value.NullToStr(), out TEnum enumStr))
            {
                return enumStr;
            }

            return default(TEnum);
        }

        internal static T ToIntCuInt<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(int));
        }

        internal static T ToIntCuDecimal<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(decimal));
        }
        internal static T ToIntCuDouble<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(double));
        }
        internal static T ToIntCuDateTime<T>(object obj)
        {
            return (T)Convert.ChangeType(obj, typeof(DateTime));
        }

        internal static T ToGuid<T>(object obj)
        {
            return (T) Convert.ChangeType(obj, typeof(Guid));
        }


        /// <summary>
        /// DataTable转换成对象的List集合
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="_tab">DataTable</param>
        /// <returns></returns>
        public static List<T> DataTableToList<T>(DataTable _tab) where T : class
        {
            List<T> _ResultList = new List<T>();
            if (_tab == null) return _ResultList;

            Load<T> _RowMap = null;
            if (!mRowMapMets.ContainsKey(typeof(T)))
            {
                DynamicMethod _Method = new DynamicMethod("DyEntity_" + typeof(T).Name, typeof(T), new Type[] { typeof(DataRow) }, typeof(T), true);
                ILGenerator _Generator = _Method.GetILGenerator();
                LocalBuilder _Result = _Generator.DeclareLocal(typeof(T));
                _Generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
                _Generator.Emit(OpCodes.Stloc, _Result);
                for (int i = 0; i < _tab.Columns.Count; i++)
                {
                    var propertyInfo = typeof(T).GetProperties().ToList().Find(p => p.Name.Equals(_tab.Columns[i].ColumnName, StringComparison.OrdinalIgnoreCase) || p.GetColumnName().Equals(_tab.Columns[i].ColumnName,StringComparison.OrdinalIgnoreCase));
                    Label endIfLabel = _Generator.DefineLabel();
                    if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
                    {
                        _Generator.Emit(OpCodes.Ldarg_0);
                        _Generator.Emit(OpCodes.Ldc_I4, i);
                        _Generator.Emit(OpCodes.Callvirt, mIsDBNullMet);
                        _Generator.Emit(OpCodes.Brtrue, endIfLabel);
                        _Generator.Emit(OpCodes.Ldloc, _Result);
                        _Generator.Emit(OpCodes.Ldarg_0);
                        _Generator.Emit(OpCodes.Ldc_I4, i);
                        _Generator.Emit(OpCodes.Callvirt, mGetValueMet);
                        if (propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string))
                            if (propertyInfo.PropertyType.IsEnum)
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToEnum", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType, Enum.GetUnderlyingType(propertyInfo.PropertyType)));
                            else if (propertyInfo.PropertyType == typeof(int?))
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuInt", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                            else if (propertyInfo.PropertyType == typeof(decimal?))
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDecimal", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                            else if (propertyInfo.PropertyType == typeof(double?))
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDouble", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                            else if (propertyInfo.PropertyType == typeof(DateTime?))
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToIntCuDateTime", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                            else if (propertyInfo.PropertyType == typeof(Guid))
                                _Generator.Emit(OpCodes.Call, typeof(FuncTable2Entity).GetMethod("ToGuid", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(propertyInfo.PropertyType));
                            else
                                _Generator.Emit(OpCodes.Call, mConvertMets[propertyInfo.PropertyType]);
                        else
                            _Generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                        _Generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                        _Generator.MarkLabel(endIfLabel);
                    }
                }
                _Generator.Emit(OpCodes.Ldloc, _Result);
                _Generator.Emit(OpCodes.Ret);
                _RowMap = (Load<T>)_Method.CreateDelegate(typeof(Load<T>));
            }
            else
                _RowMap = (Load<T>)mRowMapMets[typeof(T)];

            foreach (DataRow info in _tab.Rows)
                _ResultList.Add(_RowMap(info));
            return _ResultList;
        }

        /// <summary>
        /// 通用类型转换 Convert.ChangeType
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="conversionType"></param>
        /// <returns></returns>
        public static object ConvertType(object obj, Type conversionType)
        {
            MethodInfo methodInfo = null;
            if (conversionType.IsValueType || conversionType == typeof(string))
                if (conversionType.IsEnum)
                    methodInfo = typeof(FuncTable2Entity).GetMethod("StrToEnum", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(int?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuInt", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(decimal?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDecimal", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(double?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDouble", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(DateTime?))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToIntCuDateTime", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else if (conversionType == typeof(Guid))
                    methodInfo = typeof(FuncTable2Entity).GetMethod("ToGuid", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(conversionType);
                else
                    methodInfo = mConvertMets[conversionType];
            return methodInfo?.Invoke(obj, new object[] { obj });
        }
    }
}
